import Scene from "./Scene.js";
import { CreateProgram } from "../Utils.js";
import Obj3D from "./Obj3D.js";
import Geo from "./Geo.js";
import Mat from "./Mat.js";

const defAttr = () => ({
  //纹理对象
  texture: null,
  texture1: null,
  //深度纹理对象
  textureDepth: null,
  //帧缓冲区对象
  frameBuffer: null,
  //深度缓冲区对象
  depthBuffer: null,

  //最后的渲染队列
  lastChildren: [],
  lastDrawObjs: new Map(),

  //深度元素队列
  depthElemenets: [],
  depthIsUpdate: true,

  width: 4096,
  height: 4096,
});

export default class Frame extends Scene {
  constructor(attr) {
    super(Object.assign(defAttr(), attr));
    this._init();
  }

  _init() {
    const { gl } = this;
    this.texture = gl.createTexture();
    this.texture1 = gl.createTexture();
    this.textureDepth = gl.createTexture();

    this.frameBuffer = gl.createFramebuffer();
    this.depthBuffer = gl.createRenderbuffer();

    //加载深度信息的shader
    this._addDepthProgram();
  }

  //#region lastChildren
  //独立于渲染队列, 用于需要获取texture对象的渲染, 在渲染队列的最后进行渲染
  addLast(element) {
    const { gl, lastChildren, lastDrawObjs } = this;
    lastChildren.push(element);
    element.parent = this;
    element.init(gl);
    this.addDrawObjs(element, lastDrawObjs);

    //递归出所有子类
    let child = this._getChildren(element);
    for (let obj of child) {
      this.addDrawObjs(obj, lastDrawObjs);
    }
  }

  addDrawObjs(element, drawObjsMap) {
    super.addDrawObjs(element, drawObjsMap);
    this.depthIsUpdate = true;
  }

  removeLast(element) {
    const { lastChildren, lastDrawObjs } = this;
    const i = lastChildren.indexOf(element);

    if (i != -1) {
      this.removeDrawObjs(element, lastDrawObjs);
      children.splice(i, 1);

      //递归出所有子类
      let child = this._getChildren(element);
      for (let obj of child) {
        this.removeDrawObjs(obj, lastDrawObjs);
      }
    }
  }

  removeDrawObjs(element, drawObjsMap) {
    super.removeDrawObjs(element, drawObjsMap);
    this.depthIsUpdate = true;
  }
  //#endregion

  update(_texture) {
    const { gl, width, height } = this;
    const textureIndex = 0;
    this._createTextureBuffer(textureIndex, _texture);
    this._createFrameBuffer(textureIndex, _texture);
    this._createDepthBuffer();
    //设置渲染尺寸
    gl.viewport(0, 0, width, height);
  }
  updateOfView() {
    const { gl, width, height } = this;
    //设置渲染尺寸
    gl.viewport(0, 0, width, height);
  }

  reset() {
    const { gl } = this;
    const {
      canvas: { width, height },
    } = gl;
    gl.bindFramebuffer(gl.FRAMEBUFFER, null);
    gl.bindRenderbuffer(gl.RENDERBUFFER, null);
    gl.bindTexture(gl.TEXTURE_2D, null);
    gl.viewport(0, 0, width, height);
  }

  //#region draw
  //首次渲染
  draw([r, g, b, a] = [0, 0, 0, 1]) {
    this.update(this.texture);
    super.draw([r, g, b, a]);
    this.reset();

    return this.texture;
  }
  //二次渲染
  drawLast([r, g, b, a] = [0, 0, 0, 1]) {
    const { gl, programs, lastDrawObjs } = this;

    this.update(this.texture1);
    super.draw([r, g, b, a]); //这个地方应该不需要重新渲染一遍, 我们已经有贴图数据了
    //进行lastMap的渲染
    for (let [key, value] of lastDrawObjs) {
      const { program, attribs, uniforms } = programs.get(key);
      gl.useProgram(program);

      value.forEach((element) => {
        element.update(gl, attribs, uniforms);
        element.draw(gl);
      });
    }
    this.reset();
    return this.texture1;
  }
  //渲染深度图
  drawDepth([r, g, b, a] = [0, 0, 0, 1]) {
    const { gl, depthIsUpdate, lastDrawObjs, drawObjs, programs } = this;
    if (depthIsUpdate) {
      this.depthIsUpdate = false;
      this.depthElemenets = [];
      const create = function (ele) {
        return new Obj3D({
          geo: new Geo({
            data: { a_Position: ele.geo.data["a_Position"] },
            element: ele.geo.element,
          }),
          mat: new Mat({
            program: "depth",
            mode: ele.mat.mode,
            depthTest: ele.mat.depthTest,
            data: {
              u_PvM4: ele.mat.data["u_PvM4"],
              u_ModelM4: ele.mat.data["u_ModelM4"],
            },
          }),
        });
      };
      for (let [key, val] of drawObjs) {
        for (let ele of val) {
          const obj = create(ele);
          obj.init(gl);
          this.depthElemenets.push(obj);
        }
      }
      for (let [key, val] of lastDrawObjs) {
        for (let ele of val) {
          const obj = create(ele);
          obj.init(gl);
          this.depthElemenets.push(obj);
        }
      }
    }

    const { program, attribs, uniforms } = programs.get("depth");
    gl.useProgram(program);

    this.update(this.textureDepth);
    this.depthElemenets.forEach((element) => {
      element.update(gl, attribs, uniforms);
      element.draw(gl);
    });
    this.reset();

    return this.textureDepth;
  }
  //渲染到视图上
  drawView([r, g, b, a] = [0, 0, 0, 1]) {
    this.updateOfView();
    super.draw([r, g, b, a]);
  }
  //#endregion

  //#region setLastAttrib/Uniform
  setLastUniform(name, uniform) {
    const { lastChildren } = this;
    lastChildren.forEach((u) => {
      u.setUniform(name, uniform);
    });
  }
  setLastAttrib(name, attrib) {
    const { lastChildren } = this;
    lastChildren.forEach((u) => {
      u.setAttrib(name, attrib);
    });
  }
  setLastMap(name, map) {
    const { lastChildren } = this;
    lastChildren.forEach((u) => {
      u.setMap(name, map);
    });
  }
  //#endregion

  //#region 帧缓冲区
  //创建纹理贴图 Texture
  _createTextureBuffer(index, _texture) {
    const { gl, width, height } = this;

    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
    gl.activeTexture(gl[`TEXTURE${index}`]);

    gl.bindTexture(gl.TEXTURE_2D, _texture);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); //设置滤波器
    gl.texImage2D(
      gl.TEXTURE_2D,
      0,
      gl.RGBA,
      width,
      height,
      0,
      gl.RGBA,
      gl.UNSIGNED_BYTE,
      null
    );
  }
  //创建帧缓冲区 FrameBuffer
  _createFrameBuffer(index, _texture) {
    const { gl, texture, frameBuffer } = this;

    gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
    gl.framebufferTexture2D(
      gl.FRAMEBUFFER,
      gl.COLOR_ATTACHMENT0,
      gl.TEXTURE_2D,
      _texture,
      index
    );
  }
  //创建深度缓冲区 depthBuffer
  _createDepthBuffer() {
    const { gl, width, height, depthBuffer } = this;
    gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer);
    gl.renderbufferStorage(
      gl.RENDERBUFFER,
      gl.DEPTH_COMPONENT16,
      width,
      height
    ); //配装深度缓冲区
    gl.framebufferRenderbuffer(
      gl.FRAMEBUFFER,
      gl.DEPTH_ATTACHMENT,
      gl.RENDERBUFFER,
      depthBuffer
    ); //向缓冲区中添加渲染缓冲区
  }
  //#endregion

  //创建深度program
  _addDepthProgram() {
    const { gl } = this;
    const axisVS = `    attribute vec4 a_Position;
        uniform mat4 u_ModelM4;
        uniform mat4 u_PvM4;
        void main(){
            gl_Position = u_PvM4 * u_ModelM4 * a_Position;
        }`;
    const axisFS = `    precision mediump float;
        void main(){
            gl_FragColor = vec4(vec3(gl_FragCoord.z), 1.0);
        }`;
    this.regProgram("depth", {
      program: CreateProgram(gl, axisVS, axisFS),
      attribs: ["a_Position"],
      uniforms: ["u_PvM4", "u_ModelM4"],
    });
    return "depth";
  }
}
